﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace Dycox.Windows.Forms
{
    [
    DefaultProperty("Value"),
    DefaultBindingProperty("Value"),
    DefaultEvent("ValueChanged"),
    Designer("Dycox.Windows.Forms.Design.NumericTextBoxDesigner, Dycox.Design, Version=1.0.0.0, Culture=neutral, PublicKeyToken=862bfe4dede279ec")
    ]
    public class NumericTextBox : TextBox
    {

        public event EventHandler ValueChanged;

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new event EventHandler TextChanged;

        public NumericTextBox()
        {
            base.ImeMode = System.Windows.Forms.ImeMode.Disable;
        }

        private const int WM_SETTEXT = 0x000C, WM_PASTE = 0x0302;
        private const char DEL = '\x7f', BACKSPACE = '\x8';
        private static readonly char[] SignChars = new[] { '-', '+', '.' };

        private static decimal? GetValue(string text)
        {
            text = (text ?? string.Empty).Trim();
            int len = text.Length;

            if (len > 0)
            {
                if (len == 1)
                {
                    if (SignChars.Contains(text[0]))
                        return 0m;
                }

                decimal value;

                if (decimal.TryParse(text, out value))
                    return value;
            }

            return null;
        }

        private decimal? GetValue()
        {
            return GetValue(this.Text);
        }

        private bool IsTextValid(string text)
        {
            if (string.IsNullOrEmpty(text))
                return true;

            if (!_allowDecimals && text.IndexOf('.') >= 0)
                return false;

            decimal? test = GetValue(text);

            if (test == null) return false;

            return TestValueInRange(test.Value) == 0;
        }

        private string GetNewText(string insertedText)
        {
            string text = this.Text;
            if (string.IsNullOrEmpty(insertedText))
                return this.Text;

            StringBuilder sb = new StringBuilder(text.Length + insertedText.Length);

            int start = this.SelectionStart;
            int sellen = this.SelectionLength, len;

            if (start > 0)
                sb.Append(text.Substring(0, start));

            if (insertedText.Length == 1)
            {
                char ch = insertedText[0];

                if (ch == DEL)
                {
                    if (sellen == 0 && start < text.Length)
                    {
                        start++;
                    }
                }
                else if (ch == BACKSPACE)
                {
                    len = sb.Length;
                    if (sellen == 0 && len > 0)
                    {
                        sb.Remove(len - 1, 1);
                    }
                }
                else
                    sb.Append(ch);
            }
            else
                sb.Append(insertedText);

            sb.Append(text.Substring(start + sellen));

            return sb.ToString();
        }

        private bool InsertTextSimulation(string text)
        {
            text = GetNewText(text);
            bool ok = IsTextValid(text);

            if (!ok)
                System.Media.SystemSounds.Beep.Play();

            return ok;
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {

            char ch = e.KeyChar;
            bool valid;

            if (char.IsDigit(ch) || ch == BACKSPACE || SignChars.Contains(ch))
                valid = InsertTextSimulation(ch.ToString());
            else
                valid = Control.ModifierKeys != Keys.None;


            if (valid)
                base.OnKeyPress(e);
            else
            {
                e.Handled = true;
            }
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (keyData == Keys.Up || keyData == Keys.Down)
            {
                decimal inc = _increment;
                if (keyData == Keys.Down) inc = decimal.Negate(inc);
                inc = this.Value.GetValueOrDefault() + inc;

                if (TestValueInRange(inc) == 0)
                {
                    int start = this.SelectionStart;
                    this.Value = inc;
                    this.SelectionStart = Math.Min(start, this.TextLength);
                }

                return true;
            }
            else if (keyData == Keys.Delete)
            {
                return !InsertTextSimulation(DEL.ToString());
            }
            else if (keyData == (Keys.Control | Keys.Space))
                return true;

            bool r = base.ProcessCmdKey(ref msg, keyData);

            return r;
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_PASTE)
            {
                string text = Clipboard.GetText();

                if (!InsertTextSimulation(text))
                    return;
            }
            base.WndProc(ref m);
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), Bindable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                if (!IsTextValid(value))
                    throw new ArgumentException("The specified value for Text property should be a valid number.");

                base.Text = value;
            }
        }

        private bool _allowDecimals = true;

        [DefaultValue(true), Category("Behavior"), Description("Indicates whether this control allows decimals or not.")]
        public bool AllowDecimals
        {
            get { return _allowDecimals; }
            set
            {
                if (_allowDecimals != value)
                {
                    _allowDecimals = value;
                    if (!value)
                        this.Value = this.Value;
                }
            }
        }

        private string _displayFormat;
        [DefaultValue(""), Category("Appearance"), Description("Specifies the display format of value.")]
        public string DisplayFormat
        {
            get { return _displayFormat ?? string.Empty; }
            set
            {
                if (!string.IsNullOrWhiteSpace(value))
                {
                    try { decimal.Zero.ToString(value); }
                    catch (FormatException)
                    {
                        throw new FormatException("The specified format string " + value + " is not valid.");
                    }
                    _displayFormat = value;
                }
                else
                    _displayFormat = null;
            }
        }

        decimal? _value;

        [DefaultValue(null), Bindable(true), Category("Data"), Description("The value of this control presents.")]
        public decimal? Value
        {
            get
            {
                return _value;
            }
            set
            {
                if (!_value.Equals(value))
                {
                    string text;
                    decimal? d = value;

                    if (!_allowDecimals && d.HasValue)
                    {
                        d = Math.Floor(d.Value);
                    }

                    if (d.HasValue)
                    {
                        if (_displayFormat == null)
                            text = d.Value.ToString();
                        else
                            text = d.Value.ToString(_displayFormat);
                    }
                    else
                        text = string.Empty;

                    base.Text = text;
                }
            }
        }

        protected virtual void OnValueChanged(EventArgs e)
        {
            if (ValueChanged != null)
                ValueChanged(this, e);
        }

        protected override void OnTextChanged(EventArgs e)
        {
            decimal? newValue = GetValue();
            bool valueChanged = !newValue.Equals(_value);

            base.OnTextChanged(e);
            if (TextChanged != null)
                TextChanged(this, e);

            if (valueChanged)
            {
                if (newValue.HasValue)
                {
                    int t = TestValueInRange(newValue.Value);

                    if (t < 0) newValue = _minValue;
                    else if (t > 0) newValue = _maxValue;
                }

                _value = newValue;
                OnValueChanged(e);
            }
        }

        private int TestValueInRange(decimal value)
        {
            if (_minValue != null && value < _minValue.Value) return -1;
            if (_maxValue != null && value > _maxValue.Value) return 1;
            return 0;
                //(_minValue == null || value >= _minValue.Value) &&
                //(_maxValue == null || value <= _maxValue);
        }


        private decimal _increment = 1m;

        [DefaultValue(typeof(decimal), "1"), Category("Data"), Description("Indicates the amount to increment or decrement when user press Up or Down key.")]
        public decimal Increment
        {
            get { return _increment; }
            set
            {
                _increment = value;
            }
        }

        private decimal? _maxValue;

        [DefaultValue(null), Category("Data"), RefreshProperties(System.ComponentModel.RefreshProperties.All), Description("Indicates the maximum value for this control.")]
        public decimal? MaxValue
        {
            get { return _maxValue; }
            set
            {
                if (!_maxValue.Equals(value))
                {
                    _maxValue = value;
                    if (_value != null && value != null && _value.Value > value.Value)
                        this.Value = value;
                }
            }
        }

        private decimal? _minValue = 0m;

        [DefaultValue(typeof(Decimal), "0"), Category("Data"), RefreshProperties(System.ComponentModel.RefreshProperties.All), Description("Indicates the minimum value for this control.")]
        public decimal? MinValue
        {
            get { return _minValue; }
            set
            {
                if (!_minValue.Equals(value))
                {
                    _minValue = value;
                    if (_value != null && value != null && _value.Value < value.Value)
                        this.Value = value;
                }
            }
        }

        #region hides original members that we don't need any more.

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public override bool Multiline
        {
            get
            {
                return base.Multiline;
            }
            set
            {
                if (value == true)
                    throw new ArgumentException("The Multiline property should remain false.");

                base.Multiline = value;
            }
        }

        /// <summary>
        /// Overrided. Do not use this property any more.
        /// </summary>
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new string[] Lines
        {
            get
            {
                return new string[] { this.Text };
            }
            set
            {
                if (value == null || value.Length == 0)
                    this.Text = string.Empty;
                else
                    this.Text = value[0];
            }
        }


        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new bool UseSystemPasswordChar
        {
            get
            {
                return base.UseSystemPasswordChar;
            }
            set
            {
                base.UseSystemPasswordChar = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new char PasswordChar
        {
            get
            {
                return base.PasswordChar;
            }
            set
            {
                base.PasswordChar = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new bool WordWrap
        {
            get
            {
                return base.WordWrap;
            }
            set
            {
                base.WordWrap = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new ScrollBars ScrollBars
        {
            get
            {
                return base.ScrollBars;
            }
            set
            {
                base.ScrollBars = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new AutoCompleteMode AutoCompleteMode
        {
            get
            {
                return base.AutoCompleteMode;
            }
            set
            {
                base.AutoCompleteMode = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new AutoCompleteSource AutoCompleteSource
        {
            get
            {
                return base.AutoCompleteSource;
            }
            set
            {
                base.AutoCompleteSource = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new AutoCompleteStringCollection AutoCompleteCustomSource
        {
            get
            {
                return base.AutoCompleteCustomSource;
            }
            set
            {
                base.AutoCompleteCustomSource = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new bool AcceptsTab
        {
            get
            {
                return base.AcceptsTab;
            }
            set
            {
                base.AcceptsTab = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new bool AcceptsReturn
        {
            get
            {
                return base.AcceptsReturn;
            }
            set
            {
                base.AcceptsReturn = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced)]
        public override int MaxLength
        {
            get
            {
                return base.MaxLength;
            }
            set
            {
                base.MaxLength = value;
            }
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new CharacterCasing CharacterCasing
        {
            get { return base.CharacterCasing; }
            set { base.CharacterCasing = value; }
        }

        [DefaultValue(typeof(ImeMode), "Disable"), Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public new ImeMode ImeMode
        {
            get { return base.ImeMode; }
            set { base.ImeMode = value; }
        }

        #endregion
    }
}
